package framework.service.client;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;

import org.xml.sax.SAXException;

import framework.Trait;
import framework.server.Server;
import framework.server.ServerException;
import framework.service.AbstractService;

/**
 * Client connection manager.<br>
 * <br>
 * Provides functionality for clients to connect via socket.<br>
 * When a client is connected, the manager 
 * extracts {@link ClientRequest} by {@link Socket} and generates a sequential 
 * message {@link ClientService#process} to the first of the host {@link ClientService}s
 * registered already to the manager.
 * If a {@link ClientService} doesn't recognize the connection should calls next service
 * in order next registered service to be requested.<br>
 *
 * @author	Orlin Tomov
 * @version	0.2
 *
 */
public class ClientManager extends AbstractService {

	/** default port for clients */
	public static final int IP_PORT = 8000;
	/** ip port for clients */
	private int port = IP_PORT;
	/** the server socket */
	private ServerSocket serversocket = null;
	/** socket timeout in ms (min * sec * ms) */
	private static final int timeout = 2 * 60 * 1000; 

	/** services of client request */
	private List<String> services;
	/** max number of client threads */
	private int maxcount = 1;

	/**
	 * Creates ClientManager service
	 *
	 * @param	container
	 * @param	serviceName
	 */
	public ClientManager(Trait container, String serviceName) {
		super(container, serviceName);
		services = new ArrayList<String>();
	}

	@Override
	protected Server getContainer() {
		return (Server) super.getContainer();
	}

	public Integer getPort() {
		return port;
	}

	public void setPort(Integer port) {
		this.port = port;
	}

	public Integer getMaxcount() {
		return maxcount;
	}

	public void setMaxcount(Integer maxcount) {
		this.maxcount = maxcount;
	}

	/** do nothing */
	@Override
	public void init() throws ServerException {
	}

	/** Opens server socket at port */
	@Override
	public void starting() {
		if (serversocket == null) {
			try {
				serversocket = new ServerSocket(port);
				//log.debug("Ip port "+port);
			} catch(IOException exception) {
				/**/log.error(exception.getMessage(), exception);
				return;
			}
		}
		ClientResponse client = new ClientResponse(getContainer(), "client");
		getContainer().addService(client);
		client.starting();
		addService(client.getName(), false);
		super.starting();
	}

	/** Closes the server socket */
	@Override
	public void stoping() {
		super.stoping();
		try {
			/**/String lines = "closing client manager:\n";
			/**/StackTraceElement[] elements = Thread.currentThread().getStackTrace();
			/**/for (int i = 0; i < elements.length; i++) {
				/**/  lines += elements[i].toString() + "\n";
			/**/}
			/**/log.debug(lines);
			serversocket.close();
		} catch (Exception exception) {
			/**/log.error(exception.getMessage(), exception);
		}
		serversocket = null;
	}

	/**
	 * Accepts client connection  
	 */
	@SuppressWarnings("unused")
	@Override
	public void run() {
		try {
			init();
		while (getContainer().isRunning() && isRunning()) { 
			if (isRunning()) {
				if (serversocket == null) {
					try {
						Thread.sleep(100L);
					} catch(InterruptedException exception) {
						log.warn(exception);
					}
					starting();
				} else {
					Socket socket = null;
					try {
						/**/log.debug(getServiceName() + " is waiting");
						socket = serversocket.accept();
						clientConnected(socket);
					} catch( SocketException e) {
						if ((serversocket != null) && (serversocket.isClosed())) {
							serversocket = null;
						}
						/** socket closed */
					} catch(IOException exception) {
						/**/log.error(exception.getMessage(), exception);
						if (socket != null) {
							try {
								socket.close();
							} catch(IOException ex) {
							}
						}
					}
				}
			} else { // running
				try {
					sleep(100L);
				} catch(InterruptedException exception) {
					/**/log.error(exception.getMessage(), exception);
				}
			}
		}
		} catch (ServerException exception) {
			/**/log.error(exception.getMessage(), exception);
		}
	}

	/**
	 * Extracts {@link ClientRequest} by {@link Socket} and generates a sequential 
	 * message {@link ClientService#clientRequest} to the first of the host {@link ClientService}s.
	 * 
	 * @param	socket	of the client
	 */
	private void clientConnected(Socket socket) {
		try {
			socket.setSoTimeout(timeout);
			socket.setKeepAlive(false);
			/**/log.info("Client connected: " + socket);
			ClientRequest clientRequest = new ClientRequest(this, socket);
			clientRequest.request();
			getContainer().sendMessage(new ClientMessage(clientRequest), getName(), services.get(0));
		} catch (IOException exception) {
			if (!socket.isConnected()) {
				/**/log.error("Problem client comunication: ", exception);
			}
		} catch (SAXException exception) {
			/**/log.error("Problem client comunication: ", exception);
		}
	}

	/**
	 * Adds the service to the list of registered into the manager services.
	 *  
	 * @param	service	to be added
	 * @param	first	the position in list where it should be added
	 */
	public void addService(String service, boolean first) {
		/** rearranges services */
		if (first) {
			services.add(0, service);
		} else { // last
			services.add(service);
		}
	}

	/**
	 * send the message to the next client in list 
	 * 
	 * @param	message
	 */
	public void nextClient(ClientMessage message, String service) {
		String nextService = "";
		int index = services.indexOf(service);
		if (++index < services.size()) {
			nextService = services.get(index);
		}
		getContainer().sendMessage(message, service, nextService);
	}
}
